--[[
    Soundtrack addon for World of Warcraft

    Battle events functions.
    Functions that manage battle situation changes.
]]

local classifications = 
{
    "normal",
    "rare",
    "elite",
    "rareelite",
    "worldboss",
}

local difficulties = 
{
    "Unknown", -- Attacked
    "Trivial", -- gray
    "Easy", -- green
    "Normal", -- yellow
    "Tough", -- orange
    "Impossible", -- red
}

-- Used to determine battle event priority
local battleEvents = {
    "Unknown", -- Attacked
    "TrivialBattle", -- gray
    "EasyBattle", -- green
    "NormalBattle", -- yellow
    "ToughBattle", -- orange
    "ImpossibleBattle", -- red
    "TrivialEliteBattle", -- gray
    "EasyEliteBattle", -- green
    "NormalEliteBattle", -- yellow
    "ToughEliteBattle", -- orange
    "ImpossibleEliteBattle", -- red
    "BossBattle",
    "WorldBossBattle",
    "PvPBattle"
}

local function GetDifficultyFromText(difficultyText)
    
    -- TODO IndexOf
    for i,d in ipairs(difficulties) do
        if d == difficultyText then
            return i
        end
    end
    
    Soundtrack.TraceBattle("Cannot find difficulty : "..difficultyText)
    return 0
end

-- Returns a classification number. Used to compare classifications
local function GetClassificationLevel(classificationText)
    
    -- TODO IndexOf
    
    for i,c in ipairs(classifications) do
        if c == classificationText then
            return i
        end
    end
    
    Soundtrack.TraceBattle("Cannot find classification : "..classificationText)
    return 0
end

local function GetDifficultyText(difficultyLevel)
    return difficulties[difficultyLevel]
end

local function GetClassificationText(classificationLevel)
    return classifications[classificationLevel]
end

-- Returns the difficulty of a target based on the level of the mob, compared
-- to the players level. (also known as the color of the mob).
local function GetDifficulty(target, targetLevel)
    local playerLevel = UnitLevel("player")
    if UnitIsTrivial(target) then 
        return GetDifficultyFromText("Trivial")
    elseif playerLevel <= targetLevel-5 then
        return GetDifficultyFromText("Impossible")
    elseif playerLevel == targetLevel-4 or playerLevel == targetLevel-3 then
        return GetDifficultyFromText("Tough")
    elseif playerLevel >= targetLevel-2 and playerLevel <= targetLevel+2 then
        return GetDifficultyFromText("Normal")    
    else
        return GetDifficultyFromText("Easy")
    end
end

-- Calculates the highest enemy level amongs all the party members and pet
-- targets. Thanks to Athame!
-- Returns classification, difficulty, pvpEnabled
function GetGroupEnemyLevel()

    local prefix
    local total
    if GetNumRaidMembers() > 0 then
        prefix = "raid"
        total = GetNumRaidMembers()
    else
        prefix = "party"
        total = GetNumPartyMembers()
    end
    
    -- add your party/raid members
    local units = {} 
    table.insert(units, "player") -- you
    if total > 0 then
        local index
        for index = 1, total do 
            table.insert(units, prefix..index) 
        end
    end
    
    -- add your party/raid pets
    if UnitExists("pet") then
        table.insert(units, "pet")
    end
    
    local index
    for index = 0, total do
        if UnitExists(prefix.."pet"..index) then 
            table.insert(units, prefix.."pet"..index) 
        end
    end
    
    local unit
    local highestlevel = 1
    local highestDifficulty = 1
    local highestClassification = 1
    local pvpEnabled = false
    
    local bossTable = nil
    if not Soundtrack.Events.Stack[7].eventName then
        bossTable = Soundtrack.Events.GetTable("Boss")
    end
    
    for index,unit in ipairs(units) do
        local target = unit .. "target"
        local unitExists = UnitExists(target)
        local unitIsEnemy = not UnitIsFriend("player", target)
        local unitIsAlive = not UnitIsDeadOrGhost(target)
        
        if unitExists and unitIsEnemy and unitIsAlive then
            
            if bossTable then
                local unitName = UnitName(target)
                local bossEvent = bossTable[unitName]
                if bossEvent then
                    Soundtrack.PlayEvent("Boss", unitName)
                end
            end
            
            --Soundtrack.TraceBattle("target: " .. target)
            --Soundtrack.TraceBattle("unit name: " .. UnitName(target))
            
            -- If we find at least one target, we set the minimum to trivial instead of unknown.
            if highestDifficulty == 1 then
                highestDifficulty = 2
            end
            
            -- check for pvp
            if not pvpEnabled then
                pvpEnabled = UnitIsPlayer("target") and UnitCanAttack("player", "target")
            end
            
            -- Get the target level
            local targetlevel = UnitLevel(target)
            if targetlevel then
                if targetlevel == -1 then 
                    targetlevel = UnitLevel("player") + 10 -- at least 10 levels higher
                end
                if targetlevel > highestlevel then
                    highestlevel = targetlevel -- this unit has the highest living hostile target so far
                end
            end
            
            -- Get the target classification
            local classificationLevel = GetClassificationLevel(UnitClassification(target))
            if classificationLevel > highestClassification then
                highestClassification = classificationLevel
            end
            
            difficultyLevel = GetDifficulty(target, targetlevel)
            if difficultyLevel > highestDifficulty then
                highestDifficulty = difficultyLevel
            end
        end
    end
    
    local classificationText = GetClassificationText(highestClassification)
    local difficultyText = GetDifficultyText(highestDifficulty)
    --Soundtrack.TraceBattle("Mob detected: Difficulty: "..difficultyText..", Classification: "..classificationText)
    return classificationText, difficultyText, pvpEnabled
end





-- When the player is engaged in battle, this determines 
-- the type of battle.
local function GetBattleType()
    local classification, difficulty, pvpEnabled = GetGroupEnemyLevel()

    if pvpEnabled then
        return "PvPBattle"
    else
        local eventName = classification
        if classification == "worldboss" then
            return "WorldBossBattle"
        elseif classification == "rareelite" or
            classification == "rare" then
            return "BossBattle"
        elseif classification == "elite" then
            return difficulty.."EliteBattle"
        else
            return difficulty.."Battle"
        end
    end
    
    return "InvalidBattle"
end

local victoryPlaying = false -- To differentiate between active battle because of victory or not.

local hostileDeathCount = 0
local currentBattleTypeIndex = 0 -- Used to determine battle priorities and escalations

local function StartVictoryMusic()
    currentBattleTypeIndex = 0 -- we are out of battle
    Soundtrack.StopEventAtLevel(6)
    Soundtrack.StopEventAtLevel(7)
    if hostileDeathCount > 0 then
        -- Attempt to start victory music
        Soundtrack.PlayEvent("Misc", "Victory")
        hostileDeathCount = 0
    else
        Soundtrack.StopEvent("Misc", "Victory")
    end
end

local function StopCombatMusic()
    Soundtrack.TraceBattle("Stop Combat Music")
    StartVictoryMusic()
end


local function IsPlayerReallyDead()
    local dead = UnitIsDeadOrGhost("player")
    
    local _,class = UnitClass("player")
    if class ~= "HUNTER" then
        return dead
    end
    local i
    for i=1,24 do
        local texture = GetPlayerBuffTexture(i)
        if texture == "Interface\\Icons\\Ability_Rogue_FeignDeath" then
            dead = nil -- player is just FD, not really dead
        end
    end
    
    return dead
end

local function AnalyzeBattleSituation() 

    local battleType = GetBattleType()
    local battleTypeIndex = Soundtrack.IndexOf(battleEvents, battleType)
    -- If we're in cooldown, but a higher battle is already playing, keep that one, 
    -- otherwise we can escalate the battle music.
    --Soundtrack.TraceEvents("CurrentBattleTypeIndex: " .. currentBattleTypeIndex)
    --Soundtrack.TraceEvents("BattleTypeIndex: " .. battleTypeIndex)
    
    -- If we are out of combat, we play the battle event.
    -- If we are in combat, we only escalate if option is turned on
    if currentBattleTypeIndex == 0 or 
       (Soundtrack.Settings.EscalateBattleMusic and battleTypeIndex > currentBattleTypeIndex) then
        Soundtrack.PlayEvent("Battle", battleType)
        currentBattleTypeIndex = battleTypeIndex
    end
end


    -- True when a battle event is currently playing
Soundtrack.BattleEvents.Stealthed = false


function Soundtrack.BattleEvents.OnLoad()
    this:RegisterEvent("PLAYER_REGEN_DISABLED")
    this:RegisterEvent("PLAYER_REGEN_ENABLED")
    this:RegisterEvent("PLAYER_UNGHOST")
    this:RegisterEvent("PLAYER_ALIVE")
    this:RegisterEvent("PLAYER_DEAD")
    this:RegisterEvent("PLAYER_AURAS_CHANGED")
    this:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
    this:RegisterEvent("PLAYER_AURAS_CHANGED")
end
    
function Soundtrack.BattleEvents.OnUpdate()
    if currentBattleTypeIndex > 0 then
        AnalyzeBattleSituation()
    end
end
    
 function Soundtrack.BattleEvents.OnEvent(arg1, arg2)
    
    if event == "PLAYER_REGEN_DISABLED" then
        if not Soundtrack.Settings.EnableBattleMusic then
            return
        end

        -- If we re-enter combat after cooldown, clear timers.
        Soundtrack.Timers.Remove("BattleCooldown")
        AnalyzeBattleSituation()
    elseif event == "PLAYER_REGEN_ENABLED" and 
           not UnitIsCorpse("player") and 
           not UnitIsDead("player") then
        if Soundtrack.Settings.BattleCooldown > 0 then
            Soundtrack.Timers.AddTimer("BattleCooldown", Soundtrack.Settings.BattleCooldown, StopCombatMusic)
        else
            StopCombatMusic()            
        end
    elseif event == "COMBAT_LOG_EVENT_UNFILTERED" and arg2 == "PARTY_KILL" then
        hostileDeathCount = hostileDeathCount + 1
        Soundtrack.TraceBattle("Death count: "..hostileDeathCount)
    elseif event == "PLAYER_UNGHOST" then
        Soundtrack.StopEvent("Misc", "Ghost")    
    elseif event == "PLAYER_ALIVE" then
        Soundtrack.TraceBattle("PLAYER_ALIVE")
        if UnitIsDeadOrGhost("player") then
            Soundtrack.PlayEvent("Misc", "Ghost")
        else
            Soundtrack.StopEvent("Misc", "Ghost")
        end
    elseif event == "PLAYER_AURAS_CHANGED" then
        if UnitIsFeignDeath("player") then
            -- Stop the battle music
            Soundtrack.TraceBattle("Feign death: Stopping battle music")
            Soundtrack.StopEventAtLevel(6)
            Soundtrack.StopEventAtLevel(7)
        end
    elseif event == "PLAYER_DEAD" then
        Soundtrack.TraceBattle("PLAYER_DEAD")
        if IsPlayerReallyDead() then
            Soundtrack.PlayEvent("Misc", "Death")
        end
    end
end


function Soundtrack.BattleEvents.Initialize()
    Soundtrack.AddEvent("Battle", "UnknownBattle", 6, true)

    Soundtrack.AddEvent("Battle", "TrivialBattle", 6, true)
    Soundtrack.AddEvent("Battle", "EasyBattle", 6, true)
    Soundtrack.AddEvent("Battle", "NormalBattle", 6, true)
    Soundtrack.AddEvent("Battle", "ToughBattle", 6, true)
    Soundtrack.AddEvent("Battle", "ImpossibleBattle", 6, true)
    
    Soundtrack.AddEvent("Battle", "TrivialEliteBattle", 6, true)
    Soundtrack.AddEvent("Battle", "EasyEliteBattle", 6, true)
    Soundtrack.AddEvent("Battle", "NormalEliteBattle", 6, true)
    Soundtrack.AddEvent("Battle", "ToughEliteBattle", 6, true)
    Soundtrack.AddEvent("Battle", "ImpossibleEliteBattle", 6, true)
    
    Soundtrack.AddEvent("Battle", "BossBattle", 6, true)
    Soundtrack.AddEvent("Battle", "WorldBossBattle", 6, true)
    Soundtrack.AddEvent("Battle", "PvPBattle", 6, true)
    
    Soundtrack.AddEvent("Misc", "Victory", 6, false)
    Soundtrack.AddEvent("Misc", "Death", 6, true)
    Soundtrack.AddEvent("Misc", "Ghost", 6, true)

end